<template>
  <div class="city-container">
    <div class="message">
      <div>通过shader，实现包围线以及其上的扫光</div>
    </div>
    <div id="container" ref="container"></div>
  </div>
</template>

<script>
import { InitThree } from "../../utils/init-three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";

import {
  DirectionalLight,
  Color,
  Vector3,
  Clock,
  EdgesGeometry,
  LineSegments,
  // LineBasicMaterial,
  ShaderMaterial,
} from "three";

import Shader from "./utils/shader";

export default {
  name: "surround-line-city",
  data() {
    return {
      initThree: null,
      scene: null,
      draco: null,
      dracoLoader: null,
      clock: new Clock(),
      isStart: false,
      startTime: { value: 0 },
      time: { value: 0 },
    };
  },
  mounted() {
    this.initCity();
  },
  methods: {
    initCity() {
      this.initThree = new InitThree(this.$refs.container);
      this.scene = this.initThree.scene;
      this.addLight();
      this.loadingModel();
    },

    addLight() {
      const directionalLight = new DirectionalLight(0xffffff, 2);
      directionalLight.position.set(10, 10, 10);
      this.scene.add(directionalLight);
      // const helper = new DirectionalLightHelper(directionalLight, 5);
      // this.scene.add(helper);
    },

    loadingModel() {
      const loader = new GLTFLoader();
      this.dracoLoader = new DRACOLoader();
      this.dracoLoader.setDecoderPath("model/gltf/draco/");
      loader.setDRACOLoader(this.dracoLoader);
      loader.load(
        "model/gltf/model.gltf",
        (gltf) => {
          const group = gltf.scene;
          group.traverse((child) => {
            if (child.name == "city") {
              this.setCityMaterial(child);
              // 创建包围线
              this.addSurroundLine(child);
            }
            if (child.name == "floor") {
              child.material.color.setStyle("#111111");
            }
          });
          this.scene.add(group);
          this.isStart = true;
          this.animate();
        },
        (xhr) => {
          console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
        },
        (error) => {
          console.log(error);
        }
      );
    },

    animate() {
      const dt = this.clock.getDelta();
      this.cityAnimate(dt);
      this.initThree.render();
      requestAnimationFrame(this.animate.bind(this));
    },

    cityAnimate(dt) {
      if (dt > 1) return false;
      this.time.value += dt;
      if (this.isStart) {
        this.startTime.value += dt * 0.6;
        if (this.startTime.value >= 1) {
          this.startTime.value = 1;
          this.isStart = false;
        }
      }
    },

    setCityMaterial(child) {
      const { material, geometry } = child;
      geometry.computeBoundingBox();
      material.transparent = true;
      material.color.setStyle("#1B3045");
      material.onBeforeCompile = (shader) => {
        // 建筑特效
        this.addCityEffects(shader);
      };
    },

    addCityEffects(shader) {
      shader.uniforms.uStartTime = this.startTime;
      const vertex = `
                    uniform float uStartTime;
                    void main() {
                    `;
      const vertexPosition = `
                    vec3 transformed = vec3(position.x, position.y* uStartTime, position.z );
                `;
      shader.vertexShader = shader.vertexShader.replace(
        "void main() {",
        vertex
      );
      shader.vertexShader = shader.vertexShader.replace(
        "#include <begin_vertex>",
        vertexPosition
      );
    },

    addSurroundLine(object) {
      const geometry = new EdgesGeometry(object.geometry);
      const worldPosition = new Vector3();
      object.getWorldPosition(worldPosition);
      const { max, min } = object.geometry.boundingBox;
      const size = new Vector3(max.x - min.x, max.y - min.y, max.z - min.z);

      // 如果直接给个线性材质，是无法跟建筑一样慢慢拉伸动画，所以这里还是需要用shader
      // const line = new LineSegments(
      //   geometry,
      //   new LineBasicMaterial({ color: 0xffffff })
      // );
      const material = this.createSurroundLineMaterial({
        max,
        min,
        size,
      });
      const line = new LineSegments(geometry, material);

      line.scale.copy(object.scale);
      line.rotation.copy(object.rotation);
      line.position.copy(worldPosition);

      this.scene.add(line);
    },

    createSurroundLineMaterial({ max, min }) {
      return new ShaderMaterial({
        transparent: true,
        uniforms: {
          uColor: {
            value: new Color("#000000"),
          },
          uActive: {
            value: new Color("#ffffff"),
          },
          time: this.time,
          uOpacity: {
            value: 0.6,
          },
          uMax: {
            value: max,
          },
          uMin: {
            value: min,
          },
          uRange: {
            value: 6,
          },
          uSpeed: {
            value: 0.2,
          },
          uStartTime: this.startTime,
        },
        vertexShader: Shader.surroundLine.vertexShader,
        fragmentShader: Shader.surroundLine.fragmentShader,
      });
    },
  },
  beforeDestroy() {
    this.initThree.destroy();
    this.dracoLoader.dispose();
    this.dracoLoader = null;
  },
};
</script>

<style lang="scss" scoped>
.city-container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}
.message {
  height: 50px;
  width: 100%;
}
#container {
  flex: 1;
  width: 100%;
}
</style>
